iT邦幫忙

2022 iThome 鐵人賽

DAY 6
3

前言

這篇是昨天文章的延伸,並加入了 let/const、Block Scope(區塊作用域)等概念一起解說。


範例說明

這裡有一個範例程式碼,讀者可以先閱讀,總共宣告四個變數,其中有兩個同名的 cat 變數,只是作用域不同。

var dog = 'globalDog';
let cat = 'globalCat';

if (true) {
  let cat = 'blockCat';
  var bird = 'globalBird';

  console.log(dog);
  console.log(cat);
}

console.log(cat);
console.log(bird);

閱讀完程式碼後,來一步步分析這段程式碼的執行過程:

1. 編譯階段完成時

第一步產生全域的 Execution Context,並且放在 Call Stack 裡面,程式裡面宣告的變數不論是 var、let、const 都會在 Heap 裡面分配了記憶體位置,因此 let / const 也會有 hoisting 的特性。

用 var 宣告的變數 dog、bird 存在於 Variable Environment,它們的值為 undefined。

而 let 宣告的變數 cat 則存在於 Lexical Environment,此時 cat 尚未賦值,在實際「賦值之前」的這段期間稱為是 Temporal Dead Zone(TDZ) 暫時死區,若此時取用的話會跳出錯誤訊息。

以下段程式來說,func() 內部在 x 被賦值前都屬於 TDZ。

function func() {
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

func();

2. 執行階段

執行階段,全域變數被賦值。

3. 進入區塊作用域開始編譯

這個階段會發現到同名的 cat 變數同時存在於 Lexical Environment,不過不同的作用域下程式碼底層的運作會分出不同的區域去存放它們,所以就算同名也不會發生衝突。

讀者可以將範例程式碼的 if 判斷式這段程式碼多複製貼上幾次,然後執行程式碼,會發現還是正常執行。

if (true) {
  let cat = 'blockCat';
  var bird = 'globalBird';
}

4. 再次進入執行階段

將該賦值的變數賦值,之後的 console.log 都取得到值,所以都可以順利印出。

5. 做個小修改

最後來做個嘗試,在印出 dog 變數前我們再多宣告一個同名的變數,所以新增 var dog = 'changeGlobalDog!!!!'; 這行。

讀者可以猜猜執行結果會怎樣。

var dog = 'globalDog';
let cat = 'globalCat';

if (true) {
  let cat = 'blockCat';
  var bird = 'globalBird';

  var dog = 'changeGlobalDog!!!!';

  console.log(dog);
  console.log(cat);
}

console.log(cat);
console.log(bird);

答案是 dog 變數值被後來宣告的變數蓋掉了,而且也沒有報錯,是不是覺得挺不嚴謹的呢?所以現在都盡量使用 let/const 了。

推薦閱讀好文 [JavaScript] 你應該使用 let 而不是 var 的 3 個重要理由


圖像化 JS 執行環境

最後這裡提供 JavaScript Visualizer 這個網站,左邊是想執行的程式碼,右邊會呈現出圖形化的執行過程。

讀者可以自行在左側區塊輸入想執行的程式碼,然後點擊左上角的執行按鈕去觀看執行過程,網站內部也有提供一些像 Execution Context、Hoisting、Closures 等範例供參考。

以下是網站截圖


Hoisting 練習題

問兩個 console.log 會印出什麼? 解答放在留言區

function sayHi() {
  console.log(name);
  console.log(age);
  var name = 'Lydia';
  let age = 21;
}

sayHi();

參考資料 & 推薦閱讀

Understanding Execution Context and Execution Stack in Javascript

JavaScript execution context — from compiling to execution

JavaScript execution context — lexical environment and block scope

About "LexicalEnvironment" and "VariableEnvironment"

Variable Environment vs lexical environment

How does JavaScript's lexical environment maintain variables declarations within nested block scopes?


上一篇
Day5-JavaScript Execution Context & Hoisting & Scope Chain
下一篇
Day7-閉包(Closure)介紹
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
json_liang
iT邦研究生 5 級 ‧ 2022-09-06 10:26:46

感謝大大解析了 關於 Execution Context與變數作用域的解說

0
harry xie
iT邦研究生 1 級 ‧ 2024-02-15 14:01:50

練習題解答

分別印出 undefined 和 ReferenceError。

var 會有 hoisting,但未賦值之前使用它會有個初始值 undefined,而 let 變數雖然也有 hoisting,但不會賦予一個初始值,所以拋出錯誤。

我要留言

立即登入留言